package com.carrot.uaa.domain;

import cn.hutool.core.util.NumberUtil;
import com.carrot.uaa.common.exception.AuthException;
import com.carrot.uaa.entity.OAuth2User;
import com.carrot.uaa.enums.UserStatusEnum;
import com.carrot.uaa.infrastructure.redis.RedisOperator;
import com.carrot.uaa.property.UaaAuthLoginProperties;
import com.carrot.uaa.util.RedisUtils;
import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 认证业务规则
 */
@Service
@AllArgsConstructor
public class AuthBusinessRuleServiceImpl implements IAuthBusinessRuleService{

    private final RedisOperator<String> redisOperator;

    private final UaaAuthLoginProperties uaaAuthLoginProperties;

    @Override
    public void checkAccount(OAuth2User oAuth2User) {
        //账号不存在
        if(oAuth2User==null){
            throw new UsernameNotFoundException("帐号不存在，请输入正确的帐号");
        }
        //账号是否被禁用
        if (UserStatusEnum.CLOSE.getCode().equals(oAuth2User.getStatus())) {
            throw new AuthException( "该账户已禁用，请联系管理员查看");
        }

        //用户是否被临时冻结
        String freezeKey = RedisUtils.getLoginTemporaryFreezeKey(oAuth2User.getAccount());
        if (Boolean.TRUE.equals(redisOperator.hseKey(freezeKey))) {
            //获取冻结的剩余时间
            Long expire = redisOperator.getExpire(freezeKey, TimeUnit.SECONDS);
            int intExpire = expire == null || expire < 1 ? 1 : expire.intValue();
            intExpire = NumberUtil.ceilDiv(intExpire, 60);
            throw new AuthException("该账户已冻结，请" + intExpire + "分钟后重试！");
        }
    }

    @Override
    public void checkSmsCaptcha(String account, String reqCaptcha,String appName) {
        String captchaVerifyCountKey = RedisUtils.getLoginSmsCaptchaErrorCountKey(account);
        //获取缓存中的验证码
        String smsCaptchaKey=RedisUtils.getLoginSmsCaptchaKey(account,appName);
        String smsCaptcha = redisOperator.get(smsCaptchaKey);
        if (smsCaptcha == null) {
            redisOperator.delete(captchaVerifyCountKey);
            throw new AuthException("验证码有误，请重新输入");
        }
        //比较验证码是否正确
        if(!Objects.equals(smsCaptcha,reqCaptcha)){
            Long verifyCont = redisOperator.incr(captchaVerifyCountKey);
            verifyCont = verifyCont == null ? 1 : verifyCont;
            //超过验证次数阈值删除缓存中的验证码
            if (verifyCont >= uaaAuthLoginProperties.getSmsCaptchaVerifyCount()) {
                redisOperator.delete(smsCaptchaKey);
                redisOperator.delete(captchaVerifyCountKey);
            }
            throw new AuthException("验证码有误，请重新输入。");
        }
        //删除缓存中的验证码
        redisOperator.delete(captchaVerifyCountKey);
        redisOperator.delete(smsCaptchaKey);
    }

    @Override
    public void checkPassword(PasswordEncoder passwordEncoder, String account, String password, String reqPassword) {
        //正确直接返回
        if (passwordEncoder.matches(reqPassword, password)) {
             //重置密码错误次数
             String passwordVerifyCountKey = RedisUtils.getLoginPasswordErrorCountKey(account);
             if(redisOperator.hseKey(passwordVerifyCountKey)){
                 redisOperator.delete(passwordVerifyCountKey);
             }
             return;
        }
        //不正确进行尝试次数验证
        this.checkUserTryPasswdCount(account);
    }

    private void checkUserTryPasswdCount(String account) {
        //获取用户已尝试的次数
        String passwordVerifyCountKey = RedisUtils.getLoginPasswordErrorCountKey(account);
        Long verifyCont = redisOperator.incr(passwordVerifyCountKey);
        verifyCont = verifyCont == null ? 1 : verifyCont;

        //未超出阈值 直接抛出异常提示
        if (verifyCont <= uaaAuthLoginProperties.getTryPasswdCount()) {
            throw new AuthException("账号或密码输入有误，请重新输入");
        }

        //超过阈值 计算超出次数
        Long overCount = verifyCont - uaaAuthLoginProperties.getTryPasswdCount();

        //计算冻结时间 单位分钟。 达到阈值冻结10分钟 再次尝试冻结20分钟 以此类推
        Integer temporaryFreezeTime = uaaAuthLoginProperties.getTemporaryFreezeTime();
        Long freezeTime = overCount * temporaryFreezeTime;
        //将冻结信息缓存至redis中 并设置相应的过期时长
        String freezeKey = RedisUtils.getLoginTemporaryFreezeKey(account);
        redisOperator.set(freezeKey, String.valueOf(freezeTime), freezeTime, TimeUnit.MINUTES);

        //抛出异常提示信息
        throw new AuthException("该账户已冻结，请" + freezeTime + "分钟后重试！");
    }
}
